#include <stdlib.h>
#include <assert.h>

#include "user_mutex.h"
#include "publicFunctions.h"
#include "list.h"
#include "mlist.h"
int number[UTHREAD_MAX_THREADS];
int choosing[UTHREAD_MAX_THREADS];

void critical_start() {
	int i = uthread_self();
	int max = 0;
	int j;
	choosing[i] = 1;
	for (j = 0; j < UTHREAD_MAX_THREADS; j++)
		if (number[j] > max)
			max = number[j];
	number[i] = 1 + max;
	choosing[i] = 0;
	for (j = 0; j < UTHREAD_MAX_THREADS; j++) {
		while (choosing[j] == 1)
			uthread_yield();
		while ((number[j] != 0) && (number[j] < number[i] || (number[j]
				== number[i] && j < i)))
			uthread_yield();
	}
}
void critical_end() {
	number[uthread_self()] = 0;
}

/*
 * Main uthread_mutex implementation
 * see user_mutex.h for descriptions
 */

struct umutex_struct {
	// FIFO queue of waiting threads
	// the top thread is current owner
	list_group* group; //mutexes
	threadList* queue; // store threads waiting for mutex
	int counter; //saves the number of self locks
};



uthread_mutex_t mDatabase[UTHREAD_MAX_THREADS];

int mIsExist(uthread_mutex_t mutex) {
	if (mutex == NULL)
		return -1;

	if (mlistSearch(mutex) != NULL)
		return 1;
	return 0;
}

uthread_id mOwner(uthread_mutex_t mutex) {
	if (mutex == NULL)
		return -1;

	if (get_list_size(mutex->queue) == 0)
		return -1;
	else
		return get_element(mutex->queue);
}

int mIsLocked(uthread_mutex_t mutex) {
	if (mutex == NULL)
		return -1;

	if (mOwner(mutex) != -1)
		return 1;
	return 0;
}

int mutexQClean(uthread_id thread) {
	critical_start();
	uthread_mutex_t mutex = mDatabase[thread];
	if (mutex == NULL) {
		critical_end();
		return UTHREAD_SUCCESS;
	} // nothing to clean
	mDatabase[thread] = NULL;
	int result = removeFromList(mutex->queue, thread);
	if (result != 0) {
		critical_end();
		return UTHREAD_FAIL;
	}
	if (mOwner(mutex) != -1)
		if (wakeupSuspended(mOwner(mutex)) != UTHREAD_SUCCESS) {
			critical_end();
			return UTHREAD_FAIL;
		}
	critical_end();
	return UTHREAD_SUCCESS;
}
int uthread_mutex_init(uthread_mutex_t* mutex_p) {
	critical_start();

	if (mutex_p == NULL) {
		critical_end();
		return MUTEX_FAILURE;
	}

	uthread_mutex_t mutex = (struct umutex_struct*) calloc(1,
			sizeof(struct umutex_struct));

	if (mutex == NULL) {
		critical_end();
		return MUTEX_FAILURE;
	}

	if (initListGroup(&mutex->group, UTHREAD_MAX_THREADS) != 0) {
		free(mutex);
		critical_end();
		return MUTEX_FAILURE;
	}

	if (createNewList(mutex->group, &mutex->queue) != 0) {
		free(mutex);
		critical_end();
		return MUTEX_FAILURE;
	}
	if (mlistAdd(mutex) == MUTEX_FAILURE) {
		free(mutex);
		critical_end();
		return MUTEX_FAILURE;
	}
	mutex->counter = 0;

	*mutex_p = mutex;
	critical_end();
	return MUTEX_SUCCESS;
}

int uthread_mutex_destroy(uthread_mutex_t mutex) {
	critical_start();
	if (mutex == NULL) {
		{
			critical_end();
			return (MUTEX_INVALID);
		}
	}
	if (!mIsExist(mutex)) {
		critical_end();
		return MUTEX_UNINITIALIZED;
	}

	if (mIsLocked(mutex)) {
		critical_end();
		return MUTEX_LOCKED;
	}

	closeList(mutex->queue);
	closeListGroup(mutex->group);
	free(mutex);

	if (mlistDelete(mutex)) {
		critical_end();
		return MUTEX_FAILURE;
	}

	critical_end();
	return MUTEX_SUCCESS;
}

int uthread_mutex_lock(uthread_mutex_t mutex) {
	critical_start();
	if (mutex == NULL) {
		{
			critical_end();
			return (MUTEX_INVALID);
		}
	}

	if (mIsExist(mutex) != 1) {
		critical_end();
		return (MUTEX_UNINITIALIZED);
	}

	uthread_id thread = uthread_self();

	if (mIsLocked(mutex) == 1) {
		if (mOwner(mutex) == thread) {
			mutex->counter++;
			critical_end();
			return MUTEX_SUCCESS;
		}

		addTailList(mutex->queue, thread);
		mDatabase[uthread_self()] = mutex;
		critical_end();
		suspend_current_thread();
		critical_start();
	}

	else
		addTailList(mutex->queue, thread);

	mutex->counter++;
	mDatabase[uthread_self()] = mutex;
	critical_end();
	return MUTEX_SUCCESS;
}

int uthread_mutex_try_lock(uthread_mutex_t mutex) {
	critical_start();
	if (mutex == NULL) {
		{
			critical_end();
			return (MUTEX_INVALID);
		}
	}
	if (!mIsExist(mutex)) {
		critical_end();
		return (MUTEX_UNINITIALIZED);
	}
	uthread_id thread = uthread_self();
	if (mIsLocked(mutex)) {
		if (mOwner(mutex) == thread) {
			mutex->counter++;
			critical_end();
			return MUTEX_SUCCESS;
		}

		critical_end();
		return MUTEX_LOCKED;
	}

	else
		addTailList(mutex->queue, thread);
	mutex->counter++;
	mDatabase[uthread_self()] = mutex;
	critical_end();
	return MUTEX_SUCCESS;

}

int uthread_mutex_unlock(uthread_mutex_t mutex) {
	critical_start();
	if (mutex == NULL) {
		critical_end();
		return (MUTEX_INVALID);
	}
	if (!mIsExist(mutex)) {
		critical_end();
		return (MUTEX_UNINITIALIZED);
	}
	if (!mIsLocked(mutex)) {
		critical_end();
		return (MUTEX_UNLOCKED);
	}

	uthread_id thread = uthread_self();
	if (mOwner(mutex) != thread) {
		critical_end();
		return (MUTEX_LOCKED);
	}

	mutex->counter --;

	if (mutex->counter == 0) {
		removeFromList(mutex->queue, uthread_self());
		mDatabase[uthread_self()] = NULL;
	}

	if (mOwner(mutex) != -1 && mutex->counter == 0){
		if (wakeupSuspended(mOwner(mutex)) != UTHREAD_SUCCESS) {
			critical_end();
			return (MUTEX_FAILURE);
		}
	}
	critical_end();
	return (MUTEX_SUCCESS);
}

